//  GREKO Chess Engine
//  (c) 2002-2012 Vladimir Medvedev <vrm@bk.ru>
//  http://greko.110mb.com

//  position.cpp: position and game representation
//  modified: 05-Aug-2012

#include "eval.h"
#include "moves.h"
#include "notation.h"
#include "utils.h"

U64 Position::s_hashSide[2];
U64 Position::s_hash[64][14];
U32 Position::s_hashPawn[64][14];

void Position::Clear()
{
  _castlings = 0;
  _ep = NF;
  _fifty = 0;
  _hash = 0;
  _hashPawn = 0;
  _Kings[WHITE] = _Kings[BLACK] = 0;
  _material[WHITE] = _material[BLACK] = 0;
  _matIndex[WHITE] = _matIndex[BLACK] = 0;
  _ply = 0;
  _side = WHITE;
  _undoSize = 0;

  for (int f = 0; f < 64; f++)
  {
    _board[f] = NOPIECE;
  }

  for (int p = 0; p < 14; p++)
  {
    _bits[p] = 0;
    _count[p] = 0;
  }

  _bitsAll[WHITE] = _bitsAll[BLACK] = 0;
}

bool Position::IsGameOver(std::string& message)
{
  bool hasMoves = false;
  MoveList mvlist;
  mvlist.GenAllMoves(*this);
  for (int i = 0; i < mvlist.Size(); ++i)
  {
    if (MakeMove(mvlist[i]._mv))
    {
      hasMoves = true;
      UnmakeMove();
      break;
    }
  }
  
  if (hasMoves == false)
  {
    if (InCheck())
      message = (_side == WHITE)? "0-1 {Black mates}" : "1-0 {White mates}";
    else
      message = "1/2-1/2 {Draw: stalemate}";
    return true;
  }

  if (EstimateDraw(*this) == EVAL_THEORETICAL_DRAW)
  {
    message = "1/2-1/2 {Draw: material}";
    return true;
  }

  if (GetRepetitions() >= 3)
  {
    message = "1/2-1/2 {Draw: 3rd repetition}";
    return true;
  }

  return false;
}

U64 Position::GetAttacks(FLD to, COLOR side, U64 occ) const
{
  U64 att = 0;

  att |= Bitboard::PawnAttacks(to, side ^ 1) & Bits(PW | side);
  att |= Bitboard::KnightAttacks(to) & Bits(NW | side);
  att |= Bitboard::KingAttacks(to) & Bits(KW | side);
  att |= Bitboard::BishopAttacks(to, occ) & (Bits(BW | side) | Bits(QW | side));
  att |= Bitboard::RookAttacks(to, occ) & (Bits(RW | side) | Bits(QW | side));

  return att;
}

std::string Position::Fen() const
{
  const std::string names = "-?PpNnBbRrQqKk";
  std::string fen;
  int empty = 0;
  for (int f = 0; f < 64; ++f)
  {
    PIECE p = _board[f];
    if (p)
    {
      if (empty)
      {
        char t1[3];
        sprintf(t1, "%d", empty);
        fen += std::string(t1);
        empty = 0;
      }
      fen += names.substr(p, 1);
    }
    else
      ++empty;

    if (Col(f) == 7)
    {
      if (empty)
      {
        char t1[3];
        sprintf(t1, "%d", empty);
        fen += std::string(t1);
        empty = 0;
      }
      if (f != 63)
        fen += "/";
    }
  }

  if (_side == WHITE)
    fen += " w";
  else
    fen += " b";

  if (_castlings & 0x33)
  {
    fen += " ";
    if (_castlings & WHITE_O_O)   
      fen += "K";
    if (_castlings & WHITE_O_O_O) 
      fen += "Q";
    if (_castlings & BLACK_O_O)   
      fen += "k";
    if (_castlings & BLACK_O_O_O) 
      fen += "q";
  }
  else
    fen += " -";

  if (_ep == NF)
    fen += " -";
  else
  {
    fen += " ";
    fen += FldToStr(_ep);
  }

  return fen;
}

int Position::GetRepetitions() const
{ 
  int total = 1;
  for (int i = _undoSize - 1; i >= 0; --i)
  {
    if (_undos[i]._hash == _hash) 
    {
      ++total;
    }

    if (_undos[i]._mv == 0)
      return 0;

    if (_undos[i]._mv.Captured())
      break;

    if (_undos[i]._mv.Piece() == PW)
      break;

    if (_undos[i]._mv.Piece() == PB)
      break;
   }

  return total;
}

void Position::InitHashNumbers()
{
  RandSeed32(42);
  for (int f = 0; f < 64; f++)
  {
    for (int p = 0; p < 14; p++)
    {
      s_hash[f][p] = Rand64();
      s_hashPawn[f][p] = (p == PW || p == PB)? Rand32() : 0;
    }
  }
  s_hashSide[0] = 0;
  s_hashSide[1] = Rand64();
}

bool Position::IsAttacked(FLD to, COLOR side) const
{
  U64 x;
  FLD from;

  if (Bitboard::PawnAttacks(to, side ^ 1) & Bits(PW | side))
    return true;

  if (Bitboard::KnightAttacks(to) & Bits(NW | side))
    return true;

  if (Bitboard::KingAttacks(to) & Bits(KW | side))
    return true;

  U64 occ = _bitsAll[WHITE] | _bitsAll[BLACK];

  x = Bitboard::BishopAttacks(to) & (Bits(QW | side) | Bits(BW | side));
  while (x)
  {
    from = Bitboard::PopLSB(x);
    if ((Bitboard::Between(from, to) & occ) == 0)
      return true;
  }

  x = Bitboard::RookAttacks(to) & (Bits(QW | side) | Bits(RW | side));
  while (x)
  {
    from = Bitboard::PopLSB(x);
    if ((Bitboard::Between(from, to) & occ) == 0)
      return true;
  }

  return false;
}

bool Position::MakeMove(Move mv)
{
  Undo& undo = _undos[_undoSize++];
  undo._castlings = _castlings;
  undo._ep = _ep;
  undo._fifty = _fifty;
  undo._hash = _hash;
  undo._mv = mv;

  FLD from = mv.From();
  FLD to = mv.To();
  PIECE piece = mv.Piece();
  PIECE captured = mv.Captured();
  PIECE promotion = mv.Promotion();

  COLOR side = _side;
  COLOR opp = side ^ 1;
  _hash ^= s_hashSide[1];

  ++_fifty;
  if (captured)
  {
    _fifty = 0;
    if (to == _ep)
      Remove(to + 8 - 16 * side);
    else
      Remove(to);
  }
  Remove(from);
  Put(to, piece);

  _ep = NF;
  switch (piece)
  {
  case PW:
  case PB:
    _fifty = 0;
    if (to - from == -16 + 32 * side)
      _ep = to + 8 - 16 * side;
    else if (promotion)
    {
      Remove(to);
      Put(to, promotion);
    }
    break;

  case KW:
    _Kings[WHITE] = to;
    if (from == E1)
    {
      if (to == G1)
      {
        Remove(H1);
        Put(F1, RW);
        _castlings ^= WHITE_DID_O_O;
      }
      else if (to == C1)
      {
        Remove(A1);
        Put(D1, RW);
        _castlings ^= WHITE_DID_O_O_O;        
      }
    }
    break;

  case KB:
    _Kings[BLACK] = to;
    if (from == E8)
    {
      if (to == G8)
      {
        Remove(H8);
        Put(F8, RB);
        _castlings ^= BLACK_DID_O_O;        
      }
      else if (to == C8)
      {
        Remove(A8);
        Put(D8, RB);
        _castlings ^= BLACK_DID_O_O_O;        
      }
    }
    break;

  default:
    break;
  }

  static const U8 castlingDelta[64] =
  {
    0xf7, 0xff, 0xff, 0xff, 0xf3, 0xff, 0xff, 0xfb,
    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
    0xfd, 0xff, 0xff, 0xff, 0xfc, 0xff, 0xff, 0xfe
  };

  _castlings &= castlingDelta[from];
  _castlings &= castlingDelta[to];

  ++_ply;
  _side ^= 1;
  
  if (IsAttacked(_Kings[side], opp))
  {
    UnmakeMove();
    return false;
  }

  return true;
}

void Position::MakeNullMove()
{
  Undo& undo = _undos[_undoSize++];
  undo._castlings = _castlings;
  undo._ep = _ep;
  undo._fifty = _fifty;
  undo._mv = 0;

  _ep = NF;
  _side ^= 1;
  ++_ply;
  _hash ^= s_hashSide[1];
}

void Position::Mirror()
{
  Position old = *this;

  Clear();
  for (int f = 0; f < 64; f++)
  {
    PIECE p = old[f];
    if (p == NOPIECE)
      continue;

    Put(FLIP[f], p ^ 1);

    if (p == KW)
      _Kings[BLACK] = FLIP[f];
    else if (p == KB)
      _Kings[WHITE] = FLIP[f];
  }

  _side = old.Side() ^ 1;
  _fifty = old.Fifty();
  if (old.Ep() != NF)
    _ep = FLIP[old.Ep()];

  _ply = old.Ply();

  int bit0 = old._castlings & 0x01;
  int bit1 = (old._castlings & 0x02) >> 1;
  int bit2 = (old._castlings & 0x04) >> 2;
  int bit3 = (old._castlings & 0x08) >> 3;
  int bit4 = (old._castlings & 0x10) >> 4;
  int bit5 = (old._castlings & 0x20) >> 5;
  int bit6 = (old._castlings & 0x40) >> 6;
  int bit7 = (old._castlings & 0x80) >> 7;

  _castlings = static_cast<U8>(
                (bit5 << 7) |
                (bit4 << 6) |
                (bit7 << 5) |
                (bit6 << 4) |
                (bit1 << 3) |
                (bit0 << 2) |
                (bit3 << 1) |
                (bit2 << 0));
}


void Position::Print() const
{
  const char names[] = "-?PpNnBbRrQqKk";
  out("\n");
  for (int f = 0; f < 64; f++)
  {
    PIECE p = _board[f];
    Highlight (p && GetColor(p) == WHITE);
    out(" %c", names[p]);
    Highlight(false);
    if (Col(f) == 7)
      out("\n");
  }

  if (_undoSize)
  {
    out("\n ");
    for (int m = 0; m < _undoSize; ++m)
      out("%s ", MoveToStrLong(_undos[m]._mv));
    out("\n ");
  }
  out("\n");
}

bool Position::SetFen(const std::string& fen)
{
  if (fen.length() < 5)
    return 0;
  
  Position tmp = *this;
  Clear();

  TokenString s(fen);
  std::string token = s.GetToken();

  FLD f = A8;

  for (size_t i = 0; i < token.length(); ++i)
  {
    PIECE piece = NOPIECE;

    switch (token[i])
    {
      case 'P': piece = PW; break;
      case 'p': piece = PB; break;
      case 'N': piece = NW; break;
      case 'n': piece = NB; break;
      case 'B': piece = BW; break;
      case 'b': piece = BB; break;
      case 'R': piece = RW; break;
      case 'r': piece = RB; break;
      case 'Q': piece = QW; break;
      case 'q': piece = QB; break;
      case 'K': piece = KW; break;
      case 'k': piece = KB; break;

      case '1': case '2': case '3': case '4':
      case '5': case '6': case '7': case '8':
        f += static_cast<U8>(token[i] - '0');
        break;

      case '/':
      if (Col(f) != 0)
        f = static_cast<U8>(8 * (Row(f) + 1));
      break;

    default:
      goto ILLEGAL_FEN;
    }

    if (piece)
    {
      if (f >= 64)
        goto ILLEGAL_FEN;

      Put(f, piece);

      if (piece == KW)
        _Kings[WHITE] = f;
      else if (piece == KB)
        _Kings[BLACK] = f;
      
      ++f;
    }
  }

  token = s.GetToken();
  if (token.empty())
    goto FINAL_CHECK;

  if (token[0] == 'w')
    _side = WHITE;
  else if (token[0] == 'b')
    _side = BLACK;
  else
    goto ILLEGAL_FEN;

  _hash ^= s_hashSide[_side];

  token = s.GetToken();
  if (token.empty())
    goto FINAL_CHECK;

  for (size_t i = 0; i < token.length(); ++i)
  {
    switch (token[i])
    {
      case 'K' : _castlings |= WHITE_O_O;   break;
      case 'Q' : _castlings |= WHITE_O_O_O; break;
      case 'k' : _castlings |= BLACK_O_O;   break;
      case 'q' : _castlings |= BLACK_O_O_O; break;
      case '-': break;
      default: goto ILLEGAL_FEN;
    }
  }
  
  token = s.GetToken();
  if (token.empty())
    goto FINAL_CHECK;
  if (token == "-")
    ;
  else
  {
    _ep = StrToFld(token);
  }

  token = s.GetToken();
  if (token.empty())
    goto FINAL_CHECK;

  _fifty = atoi(token.c_str());
  if (_fifty < 0)
    _fifty = 0;

  token = s.GetToken();
  if (token.empty())
    goto FINAL_CHECK;

  _ply = (atoi(token.c_str()) - 1) * 2;
  if (_side == BLACK)
    ++_ply;

  if (_ply < 0)
    _ply = 0;

  goto FINAL_CHECK;

ILLEGAL_FEN:
  *this = tmp;
  return false;

FINAL_CHECK:
  return true;
}

void Position::UnmakeMove()
{
  if (_undoSize == 0)
    return;

  Undo& undo = _undos[--_undoSize];
  Move mv = undo._mv;
  _ep = undo._ep;
  _castlings = undo._castlings;
  _fifty = undo._fifty;

  FLD from = mv.From();
  FLD to = mv.To();
  PIECE piece = mv.Piece();
  PIECE captured = mv.Captured();
  COLOR side = _side ^ 1;

  _hash ^= s_hashSide[1];

  Remove(to);
  Put(from, piece);
  if (captured)
  {
    if (to == _ep)
      Put(to + 8 - 16 * side, captured);
    else
      Put(to, captured);
  }
  
  --_ply;
  _side ^= 1;

  if (piece == KW)
  {
    _Kings[WHITE] = from;
    if (from == E1 && to == G1)
    {
      Remove(F1);
      Put(H1, RW);
    }
    else if (from == E1 && to == C1)
    {
      Remove(D1);
      Put(A1, RW);
    }
  }
  else if (piece == KB)
  {
    _Kings[BLACK] = from;
    if (from == E8 && to == G8)
    {
      Remove(F8);
      Put(H8, RB);
    }
    else if (from == E8 && to == C8)
    {
      Remove(D8);
      Put(A8, RB);
    }
  }
}

void Position::UnmakeNullMove()
{
  if (_undoSize == 0)
    return;

  Undo& undo = _undos[--_undoSize];
  _ep = undo._ep;
  _castlings = undo._castlings;
  _fifty = undo._fifty;

  _side ^= 1;
  --_ply;
  _hash ^= s_hashSide[1];
}
